#ifndef _ABSTRACT_CAMERA_H_
#define _ABSTRACT_CAMERA_H_

#include "math/viewport.h"
#include "math/vec3.h"
#include "math/vec2.h"
#include "graphics/color.h"

#include "math/mat4x4.h"
#include "math/frustum.h"

namespace camera
{
class CamController;

///////////////////////////////////////////////////////
///	\brief	Base class for Cameras.
///         Provides functionality shared between 
///         different camera types.
///////////////////////////////////////////////////////
class AbstractCamera 
{
public:
    /**
    * Initialize camera. Provides sensible defaults
    * @param _From      Camera points From where
    * @param _To        Camera points To Where
    * @param _Up        What's the up direction ?
    * @param _FOV       What's the Field of View (Degrees)
    * @param _Aspect    What's the aspect ration
    * @param _ClipRange What's the Clip Range (near / far Z range)
    */
                        AbstractCamera( const math::Vec3& _From = math::Vec3(0, 0, 0),
                                        const math::Vec3& _To   = math::Vec3(0, 0, 1),
                                        const math::Vec3& _Up   = math::Vec3(0, 1, 0),
                                        float _FOV = 60.0f, 
                                        float _Aspect = 1.0f, 
                                        const math::Vec2& _ClipRange = math::Vec2(1, 5000));
    /**
    * Delete the camera
    */                        
    virtual             ~AbstractCamera();



    /**
    * Clear the viewport. Values are tested against internal systems.
    * @param _ClearRGBA     Should it clear color buffers ?
    * @param _ClearStencil  Should it clear stencil ?
    * @param _ClearDepth    Should it clear Depth ?
    * @param _DepthClearVal  Value to set Depth to (default's usually okay)
    * @param _StencilClearVal  Stencil value to clear to (default's usually okay)
    */                        
            bool        Clear(		bool _ClearRGBA = true, 
                                    bool _ClearStencil = true,
                                    bool _ClearDepth = true,
                                    float _DepthClearVal = 1.0f,
                                    unsigned long _StencilClearVal = 0);


    /**
    * Start a rendering pass. This should be done once per frame, per relevant camera. 
    * Clears the buffers and calls begin on the scene).
    * Make sure no other cameras are currently rendering!
    * @param _pDevice   D3D9 Device to begin rendering into
    * @param _Clear     If this flag is set, the start frame will call clear for you.
    */                        
    virtual bool        StartFrame(bool _Clear = true)    = 0;

    /**
    * Ends a frame, calling EndScene on the device
    * Make sure no other cameras are currently rendering!
    * @param _pDevice   D3D9 Device to begin rendering into
    */                        
    virtual bool        EndFrame()      = 0;


    /**
    * Copies the viewing parameters from another camera
    * @param _FromCamera Camera to clone from
    */                        
    virtual void        CloneView(const AbstractCamera& _FromCamera);

    /**
    * Reflects the supplied camera over the supplied plane
    * @param _FromCamera Camera to clone from
    */                        
    virtual void        CloneReflection(const AbstractCamera& _FromCamera, const math::Plane& _Plane);


    /// Setting view and projection matrices
    /**
    * Sets up the camera world matrix.
    * @param _From      Camera points From where
    * @param _To        Camera points To Where
    * @param _Up        What's the up direction ?
    */                        
    void                SetLookAt(const math::Vec3& _From, const math::Vec3& _To, const math::Vec3& _Up = math::Vec3(0, 1, 0));

    
    /**
    * Sets up the camera view matrix as a projective matrix
    * @param _FOV       What's the Field of View (Degrees)
    * @param _Aspect    What's the aspect ration
    * @param _ClipRange What's the Clip Range (near / far Z range)
    */
    void                SetProjection(float _FOV = 60.0f, float _Aspect = 1.0f, const math::Vec2& _ClipRange = math::Vec2(1, 1000));

    /**
    * Sets up the camera view matrix as an Orthographic projection
    * @param _Size      Size of view
    * @param _ClipRange What's the Clip Range (near / far Z range)
    */
    void                SetOrthographic(const math::Vec2& _Size, const math::Vec2& _ClipRange);

    /// Ray calculation
    /**
    * Converts pixel-space screen-coordinates to a vector in 3D space
    * Assumes that _Pos are pixel-space coordinates (0..Width, 0..height)
    * @param _Pos       point on screen to convert.
    * @return           vector in world-space.
    */
    math::Vec3          ScreenToVector( const math::Vec2& _Pos) const;

    /**
    * Similar to ScreenToVector, except coordinates are in 0..1 scale
    * @param _Pos       point on screen to convert.
    * @return           vector in world-space.
    */
	math::Vec3			ScaledScreenToVector( const math::Vec2& _Pos) const;

	/**
    * Converts a 3D point to screen-space point.
    * Z axis gives depth into screen.
    * @param _Pos       point in world-space to convert
    * @return           point in screen space, ranged 0..1 (
    */
	math::Vec3          PositionToScreen(const math::Vec3&  _Pos) const;

    /**
    * Create a Viewport. Default is NO viewport (which is faster)
    * Use this feature ONLY if you need the ability to render to a
    * subset of a surface
    * @param _Width     Width of the viewport in pixels
    * @param _Height    Height of the viewport in pixels
    * @param _X         Pixel coordinate of the upper-left corner of the viewport on the 
    *                   render target surface. Unless you want to render to a subset of the surface, 
    *                   this member can be set to 0. 
    * @param _Y         Pixel coordinate of the upper-left corner of the viewport on the 
    *                   render target surface. Unless you want to render to a subset of the surface, 
    *                   this member can be set to 0. 
    * @param _MinZ      Low Z Range. Default's okay for most applications
    * @param _MinZ      High Z Range Default's okay for most applications
    */
    void                CreateViewport( int Width, int Height,
                                        int X = 0, int Y = 0,
                                        float MinZ = 0.0f, float MaxZ = 1.0f);
    /**
    * Delete the Viewport. 
    */
    void                DeleteViewport();

    /**
     *  Attach different controller to this camera
     *
     */
	void                AttachController(CamController* pCont);
    void                AttachKBController();
    void                AttachSpaceBallController();
    /**
     *  Update any logic attached
     *
     */
    virtual void        Tick(float dt);

    /// Accessors for the matrices
    const math::Mat4x4& GetWorldMat() const         { return m_World; };
    const math::Mat4x4& GetProjectionMat() const    { return m_Projection; };
    const math::Mat4x4& GetWorldProjMat() const     { return m_WorldProj; };
    const math::Mat4x4& GetInvWorldProjMat() const  { return m_InvWorldProj; };
    const math::Mat4x4& GetInvWorldMat() const      { return m_InvWorld; };
    const math::Mat4x4& GetInvProjMat() const       { return m_InvProj; };

    /// Accessors for the vectors
    const math::Vec3&   GetPos() const              { return m_Pos; };
    const math::Vec3&   GetUp() const               { return m_Up; };
    const math::Vec3&   GetRight() const            { return m_Right; };
    const math::Vec3&   GetDir() const              { return m_Dir; };

    /// Accessors for misc
    const math::Frustum& GetFrustum() const         { return m_Frustum; };
    float                GetFOV() const             { return m_FOV; };
    float                GetAspect() const          { return m_Aspect; };
    const math::Vec2&    GetClipRange() const       { return m_ClipRange; };
	CamController*		 GetController() const		{ return m_pCamController; };

    COLOR				 GetClearColor() const      { return m_ClearColor; };
    void                 SetClearColor(COLOR _Col) { m_ClearColor = _Col; };
    
    int                 GetWidth() const            { return m_Width; };
    int                 GetHeight() const           { return m_Height; };
    bool                HasStencil() const          { return m_Stencil; };
    bool                HasZBuffer() const          { return m_ZBuffer; };

    const math::Viewport* GetViewport() const         { return m_pViewPort; };
protected:
    /**
    * Resynch any matrices that have been invalidated.
    */
    void                ResynchMatrices();



    /// World matrix info
    math::Mat4x4            m_World;        /// world matrix
    math::Vec3              m_Pos;          /// Positin of camera
    math::Vec3              m_Dir;          /// Z-axis of camera
    math::Vec3              m_Up;           /// Y-axis of camera
    math::Vec3              m_Right;        /// X-axis of camera

    /// Projection matrix info
    math::Mat4x4            m_Projection;   /// Projection matrix
    float                   m_FOV;          /// Field of view, in degrees
    float                   m_Aspect;       /// Aspect ration (usually Width / Height )
    math::Vec2              m_ClipRange;    /// Near/Far clip plane. X = Near, Y = Far
    
    /// View frustum
    math::Frustum           m_Frustum;      /// View frustrum

    /// Cached matrices
    math::Mat4x4            m_WorldProj;    /// World projection matrix
    math::Mat4x4            m_InvWorldProj; /// Inverse World Projection
    math::Mat4x4            m_InvWorld;     /// Inverse world matrix
    math::Mat4x4            m_InvProj;      /// Inverse Projection matrix

    /// Misc
    COLOR                m_ClearColor;   /// Clear color
    unsigned int            m_Height;       /// Height of camera surface
    unsigned int            m_Width;        /// Width of camera surface
    bool                    m_Stencil;      /// Has a stencil buffer ?
    bool                    m_ZBuffer;      /// Has a ZBuffer ?

    math::Viewport*           m_pViewPort;

    /// Controllers
    CamController*          m_pCamController; 

    static AbstractCamera*  s_pCurrentCamera;   /// Camera being used in current rendering context.
};


}  // end namespace camera

#endif  // _ABSTRACT_CAMERA_H_
